﻿// Copyright (c) 2012 Mokah Nnamezie & Andrew Cooper
// Neo4Net graph database - http://neo4net.codeplex.com
//
// This file is part of Neo4Net - a .Net port of the Neo4j graph database
//
// Neo4Net is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.

namespace Neo4Net.Kernel.Impl.Core
{
    using log4net;
    using log4net.Core;
    using log4net.Repository.Hierarchy;
    using Neo4Net.Compat.Atomic;
    using Neo4Net.GraphDb;
    using NUnit.Framework;
    using System;
    using System.Collections.Generic;
    using System.Threading;

    [TestFixture]
    public class TestNode : AbstractNeo4NetTestCase
    {
        [Test]
        public void TestNodeCreateAndDelete()
        {
            long nodeId = -1;
            INode node = GraphDb.CreateNode();
            nodeId = node.Id;
            GraphDb.GetNodeById(nodeId);
            node.Delete();
            ITransaction tx = Transaction;
            tx.Success();
            tx.Finish();
            Transaction = GraphDb.BeginTx();
            Assert.That(() => GraphDb.GetNodeById(nodeId), Throws.TypeOf<NotFoundException>());
        }

        [Test]
        public void TestDeletedNode()
        {
            // do some evil stuff
            INode node = GraphDb.CreateNode();
            node.Delete();
            var log = (Logger)LogManager.GetLogger("Neo4Net.Kernal.Impl.Core.NeoConstraintsListener").Logger;
            var level = log.EffectiveLevel;
            log.Level = Level.Off;
            Assert.That(() => node.SetProperty("key1", 1), Throws.Exception);
            log.Level = level;
        }

        [Test]
        public void TestNodeAddProperty()
        {
            INode node1 = GraphDb.CreateNode();
            INode node2 = GraphDb.CreateNode();
            Assert.That(() => node1.SetProperty(null, null), Throws.TypeOf<ArgumentNullException>());
            string key1 = "key1";
            string key2 = "key2";
            string key3 = "key3";
            int int1 = 1;
            int int2 = 2;
            string string1 = "1";
            string string2 = "2";

            // add property
            node1.SetProperty(key1, int1);
            node2.SetProperty(key1, string1);
            node1.SetProperty(key2, string2);
            node2.SetProperty(key2, int2);
            Assert.That(node1.HasProperty(key1), Is.True);
            Assert.That(node2.HasProperty(key1), Is.True);
            Assert.That(node1.HasProperty(key2), Is.True);
            Assert.That(node2.HasProperty(key2), Is.True);
            Assert.That(node1.HasProperty(key3), Is.False);
            Assert.That(node2.HasProperty(key3), Is.False);
            Assert.That(node1.GetProperty(key1), Is.EqualTo(int1));
            Assert.That(node2.GetProperty(key1), Is.EqualTo(string1));
            Assert.That(node1.GetProperty(key2), Is.EqualTo(string2));
            Assert.That(node2.GetProperty(key2), Is.EqualTo(int2));

            Transaction.Failure();
        }

        [Test]
        public void TestNodeRemoveProperty()
        {
            string key1 = "key1";
            string key2 = "key2";
            int int1 = 1;
            int int2 = 2;
            string string1 = "1";
            string string2 = "2";

            INode node1 = GraphDb.CreateNode();
            INode node2 = GraphDb.CreateNode();

            Assert.That(() => node1.RemoveProperty(key1), Throws.TypeOf<NotFoundException>());
            Assert.That(() => node1.RemoveProperty(null), Throws.TypeOf<ArgumentNullException>());

            node1.SetProperty(key1, int1);
            node2.SetProperty(key1, string1);
            node1.SetProperty(key2, string2);
            node2.SetProperty(key2, int2);
            Assert.That(() => node1.RemoveProperty(null), Throws.TypeOf<ArgumentNullException>());

            // test remove property
            Assert.That(node1.RemoveProperty(key1), Is.EqualTo(int1));
            Assert.That(node2.RemoveProperty(key1), Is.EqualTo(string1));
            // test remove of non exsisting property
            Assert.That(() => node2.RemoveProperty(key1), Throws.TypeOf<NotFoundException>());
            Transaction.Failure();
        }

        [Test]
        public void TestNodeChangeProperty()
        {
            string key1 = "key1";
            string key2 = "key2";
            string key3 = "key3";
            int int1 = 1;
            int int2 = 2;
            string string1 = "1";
            string string2 = "2";
            bool bool1 = true;
            bool bool2 = false;

            INode node1 = GraphDb.CreateNode();
            INode node2 = GraphDb.CreateNode();
            node1.SetProperty(key1, int1);
            node2.SetProperty(key1, string1);
            node1.SetProperty(key2, string2);
            node2.SetProperty(key2, int2);

            Assert.That(() => node1.SetProperty(null, null), Throws.TypeOf<ArgumentNullException>());

            // test change property
            node1.SetProperty(key1, int2);
            node2.SetProperty(key1, string2);
            Assert.That(node2.GetProperty(key1), Is.EqualTo(string2));
            node1.SetProperty(key3, bool1);
            node1.SetProperty(key3, bool2);
            Assert.That(node2.GetProperty(key1), Is.EqualTo(string2));
            node1.Delete();
            node2.Delete();
        }

        [Test]
        public void TestNodeChangeProperty2()
        {
            string key1 = "key1";
            int int1 = 1;
            int int2 = 2;
            string string1 = "1";
            string string2 = "2";
            bool bool1 = true;
            bool bool2 = false;
            INode node1 = GraphDb.CreateNode();
            node1.SetProperty(key1, int1);
            node1.SetProperty(key1, int2);
            Assert.That(node1.GetProperty(key1), Is.EqualTo(int2));
            node1.RemoveProperty(key1);
            node1.SetProperty(key1, string1);
            node1.SetProperty(key1, string2);
            Assert.That(node1.GetProperty(key1), Is.EqualTo(string2));
            node1.RemoveProperty(key1);
            node1.SetProperty(key1, bool1);
            node1.SetProperty(key1, bool2);
            Assert.That(node1.GetProperty(key1), Is.EqualTo(bool2));
            node1.RemoveProperty(key1);
            node1.Delete();
        }

        [Test]
        public void TestNodeGetProperties()
        {
            string key1 = "key1";
            string key2 = "key2";
            string key3 = "key3";
            int int1 = 1;
            int int2 = 1;
            string str = "3";

            INode node1 = GraphDb.CreateNode();
            Assert.That(node1.PropertyValues.GetEnumerator().MoveNext(), Is.False);
            Assert.That(() => node1.GetProperty(key1), Throws.TypeOf<NotFoundException>());
            Assert.That(() => node1.GetProperty(null), Throws.TypeOf<ArgumentNullException>());
            Assert.That(node1.HasProperty(key1), Is.False);
            Assert.That(node1.HasProperty(null), Is.False);
            node1.SetProperty(key1, int1);
            node1.SetProperty(key2, int2);
            node1.SetProperty(key3, str);
            IEnumerator<object> values = node1.PropertyValues.GetEnumerator();
            values.MoveNext();
            values.MoveNext();
            values.MoveNext();
            IEnumerator<string> keys = node1.PropertyKeys.GetEnumerator();
            keys.MoveNext();
            keys.MoveNext();
            keys.MoveNext();
            Assert.That(node1.HasProperty(key1), Is.True);
            Assert.That(node1.HasProperty(key2), Is.True);
            Assert.That(node1.HasProperty(key3), Is.True);
            try
            {
                node1.RemoveProperty(key3);
            }
            catch (NotFoundException e)
            {
                Assert.Fail("Remove of property failed.");
            }
            Assert.That(node1.HasProperty(key3), Is.False);
            Assert.That(node1.HasProperty(null), Is.False);
            node1.Delete();
        }

        [Test]
        public void TestAddPropertyThenDelete()
    {
        INode node = GraphDb.CreateNode();
        node.SetProperty( "test", "test" );
        ITransaction tx = Transaction;
        tx.Success();
        tx.Finish();
        tx = GraphDb.BeginTx();
        node.SetProperty( "test2", "test2" );
        node.Delete();
        tx.Success();
        tx.Finish();
        Transaction =  GraphDb.BeginTx();
    }

        [Test]
        public void TestChangeProperty()
        {
            INode node = GraphDb.CreateNode();
            node.SetProperty("test", "test1");
            NewTransaction();
            node.SetProperty("test", "test2");
            node.RemoveProperty("test");
            node.SetProperty("test", "test3");
            Assert.That(node.GetProperty("test"), Is.EqualTo("test3"));
            node.RemoveProperty("test");
            node.SetProperty("test", "test4");
            NewTransaction();
            Assert.That(node.GetProperty("test"), Is.EqualTo("test4"));
        }

        [Test]
        public void TestChangeProperty2()
        {
            INode node = GraphDb.CreateNode();
            node.SetProperty("test", "test1");
            NewTransaction();
            node.RemoveProperty("test");
            node.SetProperty("test", "test3");
            Assert.That(node.GetProperty("test"), Is.EqualTo("test3"));
            NewTransaction();
            Assert.That(node.GetProperty("test"), Is.EqualTo("test3"));
            node.RemoveProperty("test");
            node.SetProperty("test", "test4");
            NewTransaction();
            Assert.That(node.GetProperty("test"), Is.EqualTo("test4"));
        }

        [Test]
        public void TestNodeLockingProblem()
        {
            TestLockProblem(GraphDb.CreateNode());
        }

        [Test]
        public void TestRelationshipLockingProblem()
        {
            INode node = GraphDb.CreateNode();
            INode node2 = GraphDb.CreateNode();
            TestLockProblem(node.CreateRelationshipTo(node2, DynamicRelationshipType.WithName("lock-rel")));
        }

        private void TestLockProblem(IPropertyContainer entity)
        {
            entity.SetProperty("key", "value");
            AtomicBoolean gotTheLock = new AtomicBoolean();
            Thread thread = new Thread(() =>
            {
                ITransaction tx = GraphDb.BeginTx();
                try
                {
                    EmbeddedGraphDb.LockManager.GetWriteLock(entity);
                    gotTheLock.Value = true;
                    tx.Success();
                }
                catch (Exception e)
                {
                    Console.WriteLine(e);
                    throw e;
                }
                finally
                {
                    tx.Failure();
                }
            });
            thread.Start();
            long endTime = DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond + 5000;
            var breakwhile = false;
            while (thread.ThreadState != ThreadState.Stopped)
            {
                if (thread.ThreadState == ThreadState.WaitSleepJoin)
                {
                    foreach (var frame in new System.Diagnostics.StackTrace(thread, false).GetFrames())
                    {
                        // if we are in WAITING state in acquireWriteLock we know that we are waiting for the lock
                        if (frame.GetMethod().Name == "acquireWriteLock")
                        {
                            breakwhile = true;
                            break;
                        }
                    }
                    if (breakwhile) break;
                }
                Thread.Sleep(1);
                if (DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond > endTime) break;
            }
            bool gotLock = gotTheLock.Value;
            NewTransaction();
            Assert.That(gotLock, Is.False);
            thread.Join();
        }
    }
}
